/**
 *		Licensed to the Apache Software Foundation (ASF) under one
 *     or more contributor license agreements.  See the NOTICE file
 *     distributed with this work for additional information
 *     regarding copyright ownership.  The ASF licenses this file
 *     to you under the Apache License, Version 2.0 (the
 *     "License"); you may not use this file except in compliance
 *     with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *      Unless required by applicable law or agreed to in writing,
 *     software distributed under the License is distributed on an
 *     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *     KIND, either express or implied.  See the License for the
 *     specific language governing permissions and limitations
 *     under the License.
 *     
 *     Image Resizer Project
 */

package image.resizer;

import java.awt.image.BufferedImage;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

/**
 * Image files related logic holder.
 * Responsible for reading/writing images, and resolving them from file tree.
 *  
 * @author Ilya Dudarev
 */
public class FileManager implements IProgressInfo{
	
	private static final String CLASS_NAME = FileManager.class.getName();
	private static final Logger logger = Logger.getLogger(FileManager.class.getPackage().getName());
	private static final Level FINER_LEVEL = Level.FINER;
	
	private static final String FILE_SEPARATOR = System.getProperty("file.separator");
	
	private LinkedList<ImageDescriptor> descriptors;
	
	private boolean searchSubdirs = true;
	private ImageFileFilter fileFilter = new ImageFileFilter();
	// Progress info
	private int initialSize;
	private int currentSize;
	
	/**
	 * Setting search subdirectories flag
	 * for resolving images.
	 */
	public void setSearchSubDirs(boolean searchSubDirs) {
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.entering(CLASS_NAME, "setSearchSubDirs", String.valueOf(searchSubDirs));
		}
		this.searchSubdirs = searchSubDirs;
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.exiting(CLASS_NAME, "setSearchSubDirs");
		}
	}
	
	/**
	 * Reads image file to {@link BufferedImage} object. 
	 */
	public static BufferedImage readImage(File imageFile)
	{
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.entering(CLASS_NAME, "readImage", imageFile);
		}
		if(imageFile == null)
		{
			IllegalArgumentException iae = new IllegalArgumentException("Null file suppled for reading.");
			logger.throwing(CLASS_NAME, "readImage", iae);
			throw iae;
		}
		if(!imageFile.exists())
		{
			RuntimeException rte = new RuntimeException("Image not found for: "+imageFile.getAbsolutePath());
			logger.throwing(CLASS_NAME, "readImage", rte);
			throw rte;
		}
		BufferedImage image = null;
		try {
			image = ImageIO.read(imageFile);
		} catch (IOException ioe) {
			logger.logp(Level.INFO, CLASS_NAME, "readImage", "Failed to read image from: "+imageFile.getPath(), ioe);
		}
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.exiting(CLASS_NAME, "readImage");
		}
		return image;
	}

	/**
	 * Writes supplied image to file. 
	 * @param toWrite - image, need to be written o file.
	 * @param path - targrt image file.
	 * @param format - format for result image file.
	 */
	public static final void writeImage(BufferedImage toWrite, String path, String format)
	{
		if(logger.isLoggable(FINER_LEVEL))
		{
			Object[] params = {"toWrite", toWrite, "path", path, "format", format};
			logger.entering(CLASS_NAME, "writeImage", params);
		}
		if(toWrite == null)
		{
			IllegalArgumentException iae = new IllegalArgumentException("Null image suppled for writing.");
			logger.throwing(CLASS_NAME, "writeImage", iae);
			throw iae;
		}
		if(path == null)
		{
			IllegalArgumentException iae = new IllegalArgumentException("Null path suppled for image writing.");
			logger.throwing(CLASS_NAME, "writeImage", iae); 
			throw iae;
		}
		if(format == null)
		{
			IllegalArgumentException iae = new IllegalArgumentException("Null format string suppled for image writing.");
			logger.throwing(CLASS_NAME, "writeImage", iae);
			throw iae;
		}
		FileOutputStream fos = null;
		try
		{
			fos = new FileOutputStream(path);
	        ImageIO.write(toWrite, format , fos);
		}
		catch (IOException ioe) {
			logger.logp(Level.INFO, CLASS_NAME, "writeImage", "Failed to write image to file.", ioe);
		}
		finally
		{
			closeClosable(fos);
			toWrite = null;
		}
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.exiting(CLASS_NAME, "writeImage");
		}
	}
	
	/**
	 * Resolves images from supplied root directory,
	 * cnstructing image descriptor for each file,
	 * building tree structure in the target root directory.
	 * @param rootDir - root directory for searching the images.
	 * @param targetDir - root directory for target tree structure.
	 */
	public void initImageFileTree(String rootDir, String targetDir)
	{
		if(logger.isLoggable(FINER_LEVEL))
		{
			Object[] params = {"rootDir", rootDir, "targetDir", targetDir};
			logger.entering(CLASS_NAME, "initImageFileTree", params);
		}
		descriptors = resolveFilesDuplicateTreeTh(rootDir, targetDir);
		if(descriptors != null)
		{
			initialSize = descriptors.size();
			currentSize = initialSize; // TODO Potential synchronization problem
		}
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.exiting(CLASS_NAME, "initImageFileTree");
		}
	}
	 
	/**
	 * Get next image descriptor from the set of resolved
	 *  descriptors.
	 * @return Image descriptor or null, if no more
	 * descriptors were found.
	 */
	public ImageDescriptor getNextDescriptor()
	{
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.entering(CLASS_NAME, "getNextDescriptor");
		}
		ImageDescriptor descriptor = null;
		if(descriptors.size() > 0)
		{
			synchronized (descriptors) {
				if(descriptors.size() > 0)
				{
					descriptor = descriptors.removeFirst();
					currentSize = descriptors.size();
				}
			}
		}
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.exiting(CLASS_NAME, "getNextDescriptor", descriptor);
		}
		return descriptor;
	}
	
	/**
	 * Resolves images from supplied root directory,
	 * cnstructing image descriptor for each file,
	 * building tree structure in the target root directory.
	 * @param rootDir - root directory for searching the images.
	 * @param targetDir - root directory for target tree structure.
	 */
	private LinkedList<ImageDescriptor> resolveFilesDuplicateTreeTh(String rootDir, String targetDir)
	{
		if(logger.isLoggable(FINER_LEVEL))
		{
			Object[] params = {"rootDir", rootDir, "targetDir", targetDir};
			logger.entering(CLASS_NAME, "resolveFilesDuplicateTreeTh", params);
		}
		File rootDirFile = new File(rootDir);
		if(!rootDirFile.exists())
		{
			logger.logp(Level.WARNING, CLASS_NAME, "resolveFilesDuplicateTreeTh", "Root directory does not exist for: "+rootDir+", returning null.");
			if(logger.isLoggable(FINER_LEVEL))
			{
				logger.exiting(CLASS_NAME, "resolveFilesDuplicateTreeTh", null);
			}
			return null;
		}
		if(!rootDirFile.isDirectory())
		{
			IllegalArgumentException iae = new IllegalArgumentException(rootDir + " (wich supplied as root of directory tree) is not directory.");
			logger.throwing(CLASS_NAME, "resolveFilesDuplicateTree", iae);
			throw new IllegalArgumentException();
		}
		LinkedList<ImageDescriptor> fileDescriptors = new LinkedList<ImageDescriptor>();
		resolveSubTreeTh(rootDirFile, targetDir, fileDescriptors, searchSubdirs);
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.exiting(CLASS_NAME, "resolveFilesDuplicateTreeTh", fileDescriptors);
		}
		return fileDescriptors;
	}
	
	// Progress information
	/**
	 * Answers current descriptors set size
	 * (number of unrequested descriptors)
	 */
	public int getCurrentSize() {
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.entering(CLASS_NAME, "getCurrentSize");
			logger.exiting(CLASS_NAME, "getCurrentSize", String.valueOf(currentSize));
		}
		return currentSize;
	};
	
	/**
	 * Answers initial number of resolved descriptors. 
	 */
	public int getInitialSize() {
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.entering(CLASS_NAME, "getInitialSize");
			logger.exiting(CLASS_NAME, "getInitialSize", String.valueOf(initialSize));
		}
		return initialSize;
	}
	
	private void resolveSubTreeTh(File dir, String parentTargetDirPath, LinkedList<ImageDescriptor> fileDescriptors, boolean searchSubDirs)
	{
		if(logger.isLoggable(FINER_LEVEL))
		{
			Object[] params = {"dir", dir, "parentTargetDirPath", parentTargetDirPath, "fileDescriptors", fileDescriptors};
			logger.entering(CLASS_NAME, "resolveSubTreeTh", params);
		}
		File[] siblings = dir.listFiles(fileFilter);
		if(siblings.length == 0)
		{
			if(logger.isLoggable(FINER_LEVEL))
			{
				logger.logp(FINER_LEVEL, CLASS_NAME, "resolveSubTreeTh", "No siblings found, returnin.");
				logger.exiting(CLASS_NAME, "resolveSubTreeTh");
			}
			return;
		}
		String targetDirPath = parentTargetDirPath + FILE_SEPARATOR;  
		boolean dirExist = false;
		for(File sibling : siblings)
		{
			if(sibling.isDirectory())
			{
				if(searchSubDirs)
				{
					resolveSubTreeTh(sibling, targetDirPath+sibling.getName(), fileDescriptors, searchSubDirs);
				}
			}
			else
			{
				if(!dirExist)
				{
					File targetDir = new File(parentTargetDirPath);
					if(!targetDir.exists())
					{
						targetDir.mkdirs();
					}
					dirExist = true;
				}
				ImageDescriptor descriptor = new ImageDescriptor(sibling, targetDirPath+sibling.getName());
				fileDescriptors.add(descriptor);
			}
		}
		if(logger.isLoggable(FINER_LEVEL))
		{
			logger.exiting(CLASS_NAME, "resolveSubTreeTh");
		}
	}
	
	private static final void closeClosable(Closeable toClose)
	{
		if(toClose != null)
		{
			try
			{
				toClose.close();
			}
			catch (Exception e) {
				// TODO: handle exception
			}
			finally
			{
				toClose = null;
			}
		}
	}
	
	// TODO coment
	private class ImageFileFilter implements FileFilter
	{
		private static final String JPEG = "jpeg";
		private static final String JPG = "jpg";// TODO enum ? config ?
		
		public boolean accept(File pathname) {
			if(pathname.isFile())
			{
				return checkExtention(pathname.getName());
			}
			return searchSubdirs;
		}
		
		private boolean checkExtention(String fileName)
		{
			fileName = fileName.toLowerCase();
			if(fileName.endsWith(JPG) || fileName.endsWith(JPEG))
			{
				return true;
			}
			return false;
			// TODO make configurable
		}
	}
}
